import {type Cookie, BrowserContext, Page, HTTPRequest, PuppeteerError} from 'rebrowser-puppeteer-core'


export interface Proxy {
    host: string;
    port?: number;
    username?: string;
    password?: string;
}

export interface RespWafSession {
    cookies: Cookie[];
    headers: Record<string, string>;
}

function proxyUrl(p: Proxy) :string {
    let url = `http://${p.host}`;
    if (p.port) {
        url += `:${p.port}`;
    }
    return url
}

export function wafSession(url: string, proxy?: Proxy) :Promise<RespWafSession> {
    return new Promise((resolve, reject) => {
        if (!global.browser) return reject("browser not ready");
        if (!url.startsWith('http')) return reject("Missing url parameter");
        if (proxy && proxy.host == '') return reject("proxy host is required");
        let close = async (ctx: BrowserContext) => {
            try { await ctx.close() } catch (err) {} // ignore err
        }
        let closeWithCtx: () => {} | undefined;
        let timeout: NodeJS.Timeout | null = null;

        global.browser.createBrowserContext({
            proxyServer: proxy ? proxyUrl(proxy) : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
        }).then(async (ctx: BrowserContext) => {
            const page = await ctx.newPage();
            page.setDefaultTimeout(60 * 1000) // 1 min
            await page.setRequestInterception(true);
            page.on("request", (request: HTTPRequest) => request.continue());
            if (proxy && proxy.username && proxy.password) {
                await page.authenticate({
                    username: proxy.username,
                    password: proxy.password,
                });
            }
            return {ctx, page};
        }).then(async ({ctx, page}: {ctx: BrowserContext, page: Page})=> {
            closeWithCtx = () => close(ctx)
            timeout = setTimeout(() => { closeWithCtx(); reject('timeout') }, 100*1000)
            page.on("response", async (res) => {
                if (![200, 302].includes(res.status()) || !res.url().startsWith(url)) return
                page.waitForNavigation({waitUntil: "domcontentloaded", timeout: 60*1000}).then(async () => {
                    let headers = res.request().headers();
                    delete headers["content-type"];
                    delete headers["accept-encoding"];
                    delete headers["accept"];
                    delete headers["content-length"];
                    const cookies = await ctx.cookies()
                    if (cookies.findIndex(cookie => cookie.name == 'cf_clearance') > -1) {
                        resolve({headers, cookies});
                    } else {
                        reject("cf_clearance cookie not found");
                    }
                }).catch((e: unknown) => {
                    console.error(e);
                    if (e instanceof Error) {
                        reject(e.message)
                    } else {
                        reject(e)
                    }
                }).finally(() => {
                    closeWithCtx()
                    if (timeout) clearTimeout(timeout)
                })
            });
            const maxRetryCount = 5
            const allowRetryErr = ['ERR_EMPTY_RESPONSE', 'ERR_CONNECTION_CLOSED',
                'ERR_CONNECTION_RESET', 'ERR_SSL_PROTOCOL_ERROR']
            for (let i = 0; i < maxRetryCount; i++) {
                try {
                    await page.goto(url, { waitUntil: "domcontentloaded" });
                    break
                } catch (e) {
                    if (i == maxRetryCount-1 || !(e instanceof Error)) throw e
                    if (allowRetryErr.findIndex(errStr => e.message.includes(errStr)) === -1) throw e
                }
            }
        }).catch((e: unknown) => {
            if (closeWithCtx) closeWithCtx()
            if (timeout) clearTimeout(timeout)
            console.error(e);
            if (e instanceof Error) {
                reject(e.message)
            } else {
                reject(e)
            }
        });
    });
}

